#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <stdbool.h>
#include "gfw.h"

static gfw_cookie *gfw_cookie_parse(const char *cookie_str) {
    gfw_cookie *cookie=malloc(sizeof(gfw_cookie));
    cookie->expires=NULL;
    cookie->domain=NULL;
    cookie->path=NULL;
    cookie->next=NULL;
    char *p=(char *)cookie_str;
    bool in_key=true;
    bool in_value=false;
    uint32_t len;
    char *key,*value;
    char *key_start=p,*key_end=NULL,*value_start,*value_end=NULL;
    while (*(p)) {
        if (in_key && *(p)=='=') {
            in_value=true;
            in_key=false;
            value_start=p+1;
        } else if(in_key) {
            key_end=p;
        } else if(in_value && *(p)==';') {
            while (*(p+1)==' ') {
                ++p;
            }
            /* make sure that the length of value and key are not zero*/
            if (value_end && key_end) {
                len=key_end-key_start+1; 
                key=malloc(len+1);
                memcpy(key,key_start,len);
                key[len]='\0';

                len=value_end-value_start+1; 
                value=malloc(len+1);
                memcpy(value,value_start,len);
                value[len]='\0';
//expires=Sun, 06-Apr-2008 11:43:49 GMT; path=/; domain=surfchen.org

                if (!strcmp(key,"expires")) {
                    cookie->expires=value;
                    free(key);
                } else if (!strcmp(key,"path")) {
                    cookie->path=value;
                    free(key);
                } else if (!strcmp(key,"domain")) {
                    cookie->domain=value;
                    free(key);
                } else {
                    cookie->key=key;
                    cookie->value=value;
                }
            }
            in_key=true;
            in_value=false;
            key_start=p+1;
            value_end=NULL;
            key_end=NULL;
        } else if(in_value) {
            value_end=p;
        }
        ++p;
    }
    return cookie;
}
gfw_response *gfw_response_parse(const char *res_str) {
    gfw_response *gres=NULL;
    char *p;
    uint32_t res_len=strlen(res_str);
    if (
        res_str[0] !='H'  ||
        res_str[1] !='T'  ||
        res_str[2] !='T'  ||
        res_str[3] !='P'  ||
        res_str[4] !='/'  ||
        res_str[5] !='1'  ||
        res_str[6] !='.'  ||
        (res_str[7] !='0' && res_str[7] != '1')  ||
        res_str[8] !=' '  ||
        res_str[9] <'1'   ||
        res_str[9] >'5'   ||
        res_str[10] <'0'  ||
        res_str[10] >'9'  ||
        res_str[11] <'0'  ||
        res_str[11] >'9'  ||
        res_str[12] !=' '
    ) {
        return gres;
    }
    gres=malloc(sizeof(gfw_response));
    gres->version=malloc(4);
    gres->version[0]=res_str[5];
    gres->version[1]=res_str[6];
    gres->version[2]=res_str[7];
    gres->version[3]='\0';
    
    gres->status=malloc(4);
    gres->status[0]=res_str[9];
    gres->status[1]=res_str[10];
    gres->status[2]=res_str[11];
    gres->status[3]='\0';
    
    gres->header=NULL;
    gres->cookie=NULL;
    gres->body=NULL;

    p=&(((char *)res_str)[11]);
    bool in_key=true;
    bool in_value=false;
    char *key_start,*key_end,*value_start,*value_end,*body_start;
    char *key,*value;
    bool chunked=false;
    
    uint32_t len;
    gfw_cookie *pre_cookie=NULL,*first_cookie=NULL;
    body_start=p+1;
    while (*(++p)) {
        if (*p=='\r' && *(p+1)=='\n') {
            ++p;
            if (*(p+1)=='\r' && *(p+2)=='\n') {
                body_start=p+3;
                break;
            }
            if (in_value) {
                len=key_end-key_start+1; 
                key=malloc(len+1);
                memcpy(key,key_start,len);
                key[len]='\0';

                len=value_end-value_start+1; 
                value=malloc(len+1);
                memcpy(value,value_start,len);
                value[len]='\0';
                gres->header=kv_add(gres->header,key,value);

                free(key);
                free(value);

                if (!strcmp(key,"Transfer-Encoding") && !strcmp(value,"chunked")) {
                    chunked=true;
                }
                body_start=p;
                if (0 && !strcmp(key,"Set-Cookie")) {
                    first_cookie=gres->cookie;
                    while (gres->cookie) {
                        pre_cookie=gres->cookie;
                        gres->cookie=gres->cookie->next;
                    }
                    gres->cookie=gfw_cookie_parse(value);
                    if (first_cookie) {
                        pre_cookie->next=gres->cookie;
                        gres->cookie=first_cookie;
                    }
                }
            }
            in_key=true;
            in_value=false;
            key_start=p+1;
        }
        if (in_key && *p==':') {
            //skip leading spaces of value.
            while (*(p+1)==' ') {
                ++p;
            }
            if (*(p+1)!='\r' || *(p+2)!='\n') {
                value_start=p+1;
                in_value=true;
                in_key=false;
            }
        } else if (in_key) {
            key_end=p;
        } else if (in_value) {
            value_end=p;
        }
    }
    if (chunked) {
        bool in_chunk_len=true;
        p=body_start-1;
        char *chunk_len_start=body_start;
        char *chunk_start=NULL;
        uint8_t chunk_len_len=1;
        char *chunk_len_hex;
        uint32_t chunk_len;
        uint32_t total_body_len=0;
        while (*(++p)) {
            if (*p=='\r' && *(p+1)=='\n') {
                if (*chunk_len_start=='0') {
                    break;
                }
                chunk_len_hex=malloc(chunk_len_len+1);
                memcpy(chunk_len_hex,chunk_len_start,chunk_len_len);
                chunk_len_hex[chunk_len_len]=0;
                sscanf(chunk_len_hex, "%X", &chunk_len);
                p+=2;
                if (total_body_len==0) {
                    total_body_len=chunk_len+1;
                    gres->body=malloc(total_body_len);
                } else {
                    total_body_len+=chunk_len;
                    gres->body=realloc(gres->body,total_body_len);
                }
                memcpy(gres->body+total_body_len-chunk_len-1,p,chunk_len);
                p+=chunk_len+2;
                free(chunk_len_hex);
                chunk_len_start=p;
                chunk_len_len=0;;
            } else {
                chunk_len_len++;
            }
        }
    } else {
        len=&res_str[res_len-1]-body_start+1;
        gres->body=malloc(len+1);
        memcpy(gres->body,body_start,len);
        gres->body[len]=0;
    }
    return gres; 
}

bool gfw_response_free(gfw_response *gres) {
    gfw_cookie *current_cookie;
    free(gres->version);
    free(gres->status);
    if (gres->body) {
        free(gres->body);
    }
    kv_free(gres->header);
    while (gres->cookie) {
        current_cookie=gres->cookie;
        gres->cookie=gres->cookie->next;
        free(current_cookie->key);
        free(current_cookie->value);
        if (current_cookie->expires) {
            free(current_cookie->expires);
        }
        if (current_cookie->domain) {
            free(current_cookie->domain);
        }
        if (current_cookie->path) {
            free(current_cookie->path);
        }
        free(current_cookie);
    }
    free(gres);
    return true;
}
